#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <png.h>

static struct fb_fix_screeninfo fb_fix;
static struct fb_var_screeninfo fb_var;
static unsigned short *screen_base = NULL;        //映射后的显存基地址

static int show_png_image(const char *path)
{
    png_structp png_ptr = NULL;     //用于存储 PNG解码/编码 过程中的上下文信息
    png_infop info_ptr = NULL;      //描述 png图像 的信息
    FILE *png_file = NULL;          //文件描述符
    unsigned short *fb_line_buf = NULL; //行缓冲区:用于存储写入到LCD显存的一行数据
    unsigned int min_h, min_w;      //能在 LCD屏幕 上显示的最小 高度和宽度（像素）
    unsigned int valid_bytes;       //显示图片时，每行占用的大小（字节）
    unsigned int image_h, image_w;  //图片信息（像素）
    png_bytepp row_pointers = NULL; //png_bytepp -> unsigned char **，指针数组
    unsigned int bytes_per_pixel = fb_var.bits_per_pixel / 8;   //LCD屏上每个像素点，占用的字节数
    unsigned int width = fb_fix.line_length / bytes_per_pixel;  //LCD屏 每行的大小（字节）
    unsigned short *base = screen_base;        //显存起始地址
    int i,j,k;

    /* 打开png文件 */
    png_file = fopen(path, "r");    //以只读方式打开
    if (NULL == png_file) {
        perror("fopen error");
        return -1;
    }

    /* 分配和初始化png_ptr、info_ptr */
    /* PNG_LIBPNG_VER_STRING --> 版本信息 */
    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_ptr) {
        fclose(png_file);   //关闭文件
        return -1;
    }

    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
        png_destroy_read_struct(&png_ptr, NULL, NULL);  //创建失败，销毁 png_structp 对象
        fclose(png_file);   //关闭文件
        return -1;
    }

    /* 设置错误返回点 */
    /* png_jmpbuf(png_ptr)，作用是从 png_ptr 中获取一个 jmp_buf 类型的错误跳转缓冲区 */
    if (setjmp(png_jmpbuf(png_ptr))) {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        fclose(png_file);
        return -1;
    }

    /* 指定数据源 文件输入流 */
    png_init_io(png_ptr, png_file);

    /* 读取png文件 */
    png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_ALPHA, NULL);
    image_h = png_get_image_height(png_ptr, info_ptr);
    image_w = png_get_image_width(png_ptr, info_ptr);
    printf("图片分辨率: %d*%d\n", image_w, image_h);

    /* 判断是不是RGB888 传入的图片必须为24位深的 */
    if ((8 != png_get_bit_depth(png_ptr, info_ptr)) &&
        (PNG_COLOR_TYPE_RGB != png_get_color_type(png_ptr, info_ptr))) {
        printf("Error: Not 8bit depth or not RGB color");
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        fclose(png_file);
        return -1;
    }

    /* 判断图像和LCD屏那个的分辨率更低 */
    if (image_w > fb_var.xres)
        min_w = fb_var.xres;
    else
        min_w = image_w;

    if (image_h > fb_var.yres)
        min_h = fb_var.yres;
    else
        min_h = image_h;

    valid_bytes = min_w * bytes_per_pixel;

    /* 读取解码后的数据 */
    fb_line_buf = malloc(valid_bytes);
    row_pointers = png_get_rows(png_ptr, info_ptr);//获取数据 --> high-level 方式

    unsigned int temp = min_w * 3;  //RGB888 一个像素3个bit位
    for(i = 0; i < min_h; i++) {
        // RGB888转为RGB565
        for(j = k = 0; j < temp; j += 3, k++) {
            fb_line_buf[k] = ((row_pointers[i][j] & 0xF8) << 8) |
                ((row_pointers[i][j+1] & 0xFC) << 3) |
                ((row_pointers[i][j+2] & 0xF8) >> 3);
        }   //fb_line_buf[k] -> 访问该行的每个像素点

        memcpy(base, fb_line_buf, valid_bytes);//将一行数据刷入显存
        base += width;   //定位到显存下一行
    }

    /* 结束、销毁/释放内存 */
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    free(fb_line_buf);
    fclose(png_file);
    return 0;
}

int main(int argc, char *argv[])
{
    unsigned int screen_size;
    int fd;

    /* 传参校验 */
    if (2 != argc) {    /* 传入图片的路径 */
        fprintf(stderr, "usage: %s <png_file>\n", argv[0]);
        exit(-1);
    }

    /* 打开framebuffer设备 */
    if (0 > (fd = open("/dev/fb0", O_RDWR))) {
        perror("open error");
        exit(EXIT_FAILURE);
    }

    /* 获取 LCD屏幕 参数信息 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
    screen_size = fb_fix.line_length * fb_var.yres;

    /* 将显示缓冲区映射到进程地址空间 */
    /* MAP_SHARED：共享映射。对映射区域的修改会同步到文件 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == (void *)screen_base) {
        perror("mmap error");
        close(fd);
        exit(EXIT_FAILURE);
    }

    /* 显示png图片 */
    memset(screen_base, 0xFF, screen_size);//屏幕刷白
    show_png_image(argv[1]);    //调用显示函数

    /* 退出 */
    munmap(screen_base, screen_size);  //取消映射
    close(fd);  //关闭文件
    exit(EXIT_SUCCESS);    //退出进程
}
